package org.budo.graph.druid.spring.web.interceptor;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.budo.support.mvcs.MvcsHandlerInterceptor;
import org.budo.support.spring.web.util.HandlerUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import com.alibaba.druid.support.http.WebStatFilter.StatHttpServletResponseWrapper;
import com.alibaba.druid.support.http.stat.WebAppStat;
import com.alibaba.druid.support.http.stat.WebRequestStat;
import com.alibaba.druid.support.http.stat.WebSessionStat;
import com.alibaba.druid.support.http.stat.WebURIStat;
import com.alibaba.druid.support.profile.ProfileEntryKey;
import com.alibaba.druid.support.profile.ProfileEntryReqStat;
import com.alibaba.druid.support.profile.Profiler;

/**
 * @author lmw
 * @see com.alibaba.druid.support.http.WebStatFilter
 * @see com.alibaba.druid.support.spring.mvc.StatHandlerInterceptor
 */
public class BudoGraphDruidSpringWebInterceptor extends AbstractBudoGraphDruidSpringWebInterceptor implements HandlerInterceptor {
    private static final Logger log = LoggerFactory.getLogger(BudoGraphDruidSpringWebInterceptor.class);

    private static final String REQUEST_URI_IN_THREAD_LOCAL_KEY = "requestURI";

    private static final String SESSION_STAT_IN_THREAD_LOCAL_KEY = "sessionStat";

    private static final ThreadLocal<Map<String, Object>> REQUEST_THREAD_LOCAL = new InheritableThreadLocal<Map<String, Object>>();

    public BudoGraphDruidSpringWebInterceptor() {
        this.setProfileEnable(true); // 默认值
        this.setExclusions("/source/*"); // *.js,*.map,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*,/druid_status/*,/source/*

        log.info("#49 this=" + this);
    }

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        MvcsHandlerInterceptor.preHandle_0(request, response, handler);

        if (null == handler || !(handler instanceof HandlerMethod)) {
            return true; // 静态资源 跳过 不统计
        }

        String requestURI = HandlerUtil.getHandlerRequestURI(request, handler);
        if (this.isExclusion(requestURI)) {
            return true; // 静态资源 跳过 不统计
        }

        // 开始
        WebAppStat webAppStat = this.getWebAppStat(request);

        long startNano = System.nanoTime();
        long startMillis = System.currentTimeMillis();

        WebRequestStat requestStat = new WebRequestStat(startNano, startMillis);
        WebRequestStat.set(requestStat);

        WebSessionStat sessionStat = this.getSessionStat(request);
        webAppStat.beforeInvoke();

        WebURIStat uriStat = webAppStat.getURIStat(requestURI, false);

        if (uriStat == null) {
            int index = requestURI.toLowerCase().indexOf(";jsessionid=");
            if (index != -1) {
                requestURI = requestURI.substring(0, index);
                uriStat = webAppStat.getURIStat(requestURI, false);
            }
        }

        if (this.isProfileEnable()) {
            Profiler.initLocal();
            Profiler.enter(requestURI, Profiler.PROFILE_TYPE_WEB);
        }

        // 第一次访问时，uriStat这里为null，是为了防止404攻击。
        if (uriStat != null) {
            uriStat.beforeInvoke();
        }

        // 第一次访问时，sessionId为null，如果缺省sessionCreate=false，sessionStat就为null。
        if (sessionStat != null) {
            sessionStat.beforeInvoke();
        }

        // set to threadLocal
        Map<String, Object> threadLocalMap = new HashMap<String, Object>();
        threadLocalMap.put(SESSION_STAT_IN_THREAD_LOCAL_KEY, sessionStat);
        threadLocalMap.put(REQUEST_URI_IN_THREAD_LOCAL_KEY, requestURI);

        REQUEST_THREAD_LOCAL.set(threadLocalMap);

        return true;
    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception {
        Map<String, Object> threadLocalMap = REQUEST_THREAD_LOCAL.get();

        WebRequestStat requestStat = WebRequestStat.current();
        WebURIStat uriStat = WebURIStat.current();
        WebAppStat webAppStat = WebAppStat.current();

        if (null == threadLocalMap || null == requestStat) {
            return; // 静态资源 跳过 不统计
        }

        WebSessionStat sessionStat = (WebSessionStat) threadLocalMap.get(SESSION_STAT_IN_THREAD_LOCAL_KEY);
        String requestURI = (String) threadLocalMap.get(REQUEST_URI_IN_THREAD_LOCAL_KEY);

        long endNano = System.nanoTime();
        requestStat.setEndNano(endNano);

        long nanos = endNano - requestStat.getStartNano();

        if (null != webAppStat) {
            webAppStat.afterInvoke(exception, nanos);
        }

        if (sessionStat == null) {
            sessionStat = this.getSessionStat(request);
            if (sessionStat != null) {
                sessionStat.beforeInvoke(); // 补偿
            }
        }

        if (sessionStat != null) {
            sessionStat.afterInvoke(exception, nanos);
            sessionStat.setPrincipal(this.getPrincipal(request));
        }

        if (uriStat == null) {
            StatHttpServletResponseWrapper responseWrapper = new StatHttpServletResponseWrapper(response);

            int status = responseWrapper.getStatus();
            if (status == HttpServletResponse.SC_NOT_FOUND) {
                String errorUrl = this.getContextPath() + "error_" + status;
                uriStat = this.getWebAppStat(request).getURIStat(errorUrl, true);
            } else {
                uriStat = this.getWebAppStat(request).getURIStat(requestURI, true);
            }

            if (uriStat != null) {
                uriStat.beforeInvoke(); // 补偿调用
            }
        }

        if (uriStat != null) {
            uriStat.afterInvoke(exception, nanos);
        }

        WebRequestStat.set(null);

        if (this.isProfileEnable() && null != Profiler.current()) { // isProfileEnable // TODO Profiler.current 为啥会为空呢
            Profiler.release(nanos);

            Map<ProfileEntryKey, ProfileEntryReqStat> requestStatsMap = Profiler.getStatsMap();
            if (uriStat != null) {
                uriStat.getProfiletat().record(requestStatsMap);
            }

            Profiler.removeLocal();
        }

        REQUEST_THREAD_LOCAL.remove();
    }

    @Override
    public String getPrincipal(HttpServletRequest httpRequest) {
        try {
            return super.getPrincipal(httpRequest);
        } catch (Throwable e) {
            if (("" + e).contains("UnknownSessionException")) {
                return "UnknownSession";
            }

            throw new RuntimeException("#186 getPrincipal error, e=" + e, e);
        }
    }
}